home *** CD-ROM | disk | FTP | other *** search
/ SGI Hot Mix 17 / Hot Mix 17.iso / HM17_SGI / research / examples / misc / spiro.pro < prev    next >
Text File  |  1997-07-08  |  36KB  |  951 lines

  1. ; $Id: spiro.pro,v 1.8 1997/01/15 04:21:02 ali Exp $
  2. ;
  3. ; Copyright (c) 1991-1997, Research Systems, Inc.  All rights reserved.
  4. ;    Unauthorized reproduction prohibited.
  5. ;+
  6. ; NAME:
  7. ;    SPIRO
  8. ; PURPOSE:
  9. ;    A Widget front end to draw "Spirograph" (TM) patterns
  10. ; CATEGORY:
  11. ;    Line Drawing, widgets
  12. ; CALLING SEQUENCE:
  13. ;    SPIRO
  14. ; OUTPUTS:
  15. ;    None.
  16. ; COMMON BLOCKS:
  17. ;    None.
  18. ; RESTRICTIONS:
  19. ;    None.
  20. ; SIDE EFFECTS:
  21. ;    Draws a "Spirograph" (TM) pattern on the current window.
  22. ;    As the original C program states: "The pattern is produced
  23. ;    by rotating a circle inside of another circle with a pen a
  24. ;    set distance inside the center of the rotating circle".
  25. ; MODIFICATION HISTORY:
  26. ;    22, December, 1989, A.B. Inspired by a C program posted
  27. ;                 to the Internet by Paul Schmidt (2/2/1988).
  28. ;       2,  February, 1995, WSO  Updated the UI and added cams to help visualize
  29. ;                                spirograph drawing.
  30. ;
  31. ;-
  32.  
  33.  
  34.     ;
  35.     ; Return the correct color depending on the color depth used.  If !D.N_COLORS
  36.     ; is greater than 256, then the output device is in true color mode.  If that's
  37.     ; the case, we want to use the same index from the red, green and blue color
  38.     ; tables to get the desired color.  If it's not true color than just return 
  39.     ; the color table index.
  40.     ;
  41. FUNCTION GetColor, colorIndex
  42.  
  43.    IF (!D.N_COLORS GT 256) THEN $
  44.       RETURN, (colorIndex * 256L + colorIndex) * 256L + colorIndex $
  45.    ELSE $
  46.       RETURN, colorIndex
  47. END
  48.  
  49.  
  50. ;================================================================================
  51. ;
  52. ;  The following five routines perform the draw operation to display the
  53. ;  spirograph and cams (if needed).
  54. ;
  55. ;  DrawFixedCam         - Draws the cam that is fixed in place
  56. ;  DrawRotatingCam      - Draws the cam that rotates about the fixed cam
  57. ;  DisplayCams          - Either shows or hides the cams
  58. ;  DrawSpirographAndCam - Draws a segment of the spirograph and calls the
  59. ;                         above routines to draw the cams
  60. ;  DrawSpirograph       - Draws the complete spirograph in one call
  61. ;
  62. ;     DrawSpirographAndCam is called to simulate
  63. ;  the animation of the cams rotating and the pen drawing out the spirograph.
  64. ;  Each call to DrawSpirographAndCam draws a frame of the animation.  This
  65. ;  includes the cams, pen and one line segment of the spirograph.
  66. ;     Animation can be simulated either though a loop or by way of a timer event.
  67. ;  When optimum animation speed is required each frame can be drawn at each
  68. ;  iteration of a tight loop (i.e.WHILE).  The timer event method activates
  69. ;  timer events at desired intervals. As each timer event occurs,
  70. ;  another frame of the animation is drawn.
  71. ;     Both methods have pluses and minuses.  A loop is used to obtain maximum
  72. ;  speed, since none of the timer event processing overhead is needed.  On
  73. ;  the other hand, since no events are processed during this loop, the user
  74. ;  can't gracefully interupt the animation process and must wait until the
  75. ;  animation has completed.  The opposite is true for the timer event method.
  76. ;  The timer event method also allows you to vary the animation speed by setting
  77. ;  different delay times for each timer event to occur.  Only the timer event
  78. ;  method is used in this example.
  79. ;
  80. ;     If the cams are not visible when the drawing is initiated, no animation
  81. ;  is used to draw the spirograph.  Therefore the DrawSpirograph
  82. ;  routine is called instead of DrawSpirographAndCam.  This routine simply
  83. ;  draws out the complete spirograph in one statement.
  84. ;
  85. ;================================================================================
  86.  
  87.     ;
  88.     ; Draw fixed cam
  89.     ;
  90. PRO DrawFixedCam, fixedAngles, fixedRadius, centerX, centerY
  91.  
  92.      ; Draw the fixed cam in black
  93.      ; This is the second index in the current color table
  94.    camColor = 0
  95.  
  96.      ; Mark the center of the cam with a small black square
  97.      ; NOTE: the fixed cam's center is always located at the draw area's center
  98.    PLOTS, [centerX-1, centerX-1, centerX+1, centerX+1, centerX-1], $
  99.           [centerY-1, centerY+1, centerY+1, centerY-1, centerY-1], $
  100.           COLOR=GetColor(camColor), /DEVICE, THICK=1
  101.  
  102.      ; Draw the fixed radius cam by drawing line segments about the center
  103.      ; at intervals of "fixedAngles"
  104.    PLOTS, fixedRadius*COS(fixedAngles)+centerX, $
  105.           fixedRadius*SIN(fixedAngles)+centerY,$
  106.           COLOR=GetColor(camColor), /DEVICE, THICK=2
  107. END
  108.  
  109.  
  110.     ;
  111.     ; Draw the rotating cam at an angle about the fixed cam and draw
  112.     ; the pen at an angle about the rotating cam
  113.     ;
  114. PRO DrawRotatingCam, rotatingAngles, penCenterX, penCenterY, $
  115.            rotatingCamCenterX, rotatingCamCenterY, penColor, $
  116.            rotatingRadius
  117.  
  118.    rCamColor = 30 ; purple
  119.  
  120.      ; Draw the rotating cam by drawing line segments about the center
  121.      ; at intervals of "rotatingAngles"
  122.    PLOTS, COS(rotatingAngles) * rotatingRadius + rotatingCamCenterX, $
  123.           SIN(rotatingAngles) * rotatingRadius + rotatingCamCenterY, $
  124.           COLOR=GetColor(rCamColor), /DEVICE, THICK=2
  125.  
  126.      ; Draw the pen tip (drawing end) as a square
  127.    PLOTS, [penCenterX-1, penCenterX-1, penCenterX+1, penCenterX+1, penCenterX-1], $
  128.           [penCenterY-1, penCenterY+1, penCenterY+1, penCenterY-1, penCenterY-1], $
  129.           COLOR=GetColor(penColor), /DEVICE, THICK=2
  130.  
  131.      ; Draw the pen arm - a line to the pen tip
  132.    PLOTS, [rotatingCamCenterX, penCenterX], [rotatingCamCenterY, penCenterY],$
  133.           COLOR=GetColor(penColor), /DEVICE, THICK=2
  134. END
  135.  
  136.  
  137.     ;
  138.     ; Show or Hide the cams only
  139.     ;
  140.     ; The spirograph and cams are drawn using offscreen pixmaps.  This gives
  141.     ; the animation a much smoother appearance.  Without using pixmaps, the
  142.     ; erasing and drawing procedures would tend to have a flashing appearance.
  143.     ; The spirograph is drawn to its own pixmap called "spiroGraphPixmap". This
  144.     ; pixmap is then copied to another "work area" pixmap where the cams are
  145.     ; drawn in.  This one is called "completePixmap".  After the cams are added
  146.     ; this pixmap is then copied to the draw area of the window.  If cams are
  147.     ; to be hidden, just the spirograph pixmap is copied to the draw area of
  148.     ; the window.
  149.     ;
  150. PRO DisplayCams, state
  151.  
  152.      ; If hiding the cams -
  153.    IF NOT state.showCams THEN BEGIN
  154.  
  155.         ; Make the display window the current graphics window
  156.       WSET, state.currentDrawArea
  157.  
  158.         ; Copy spirograph from its pixmap to the display area
  159.       DEVICE, COPY=[0, 0, 256, 256, 0, 0, state.spiroGraphPixmap]
  160.  
  161.    ENDIF ELSE BEGIN     ; Show the cams
  162.  
  163.         ; Make the complete pixmap the current graphics output pixmap
  164.       WSET, state.completePixmap
  165.  
  166.         ; First we want to add the spirograph and then add the
  167.         ; cams on top of it, so...
  168.         ; Copy the pixmap of the spirograph to the complete pixmap
  169.       DEVICE, COPY=[0, 0, 256, 256, 0, 0, state.spiroGraphPixmap]
  170.  
  171.         ; Draw rotating cam, at its new location, with the pen
  172.         ; position in the complete pixmap
  173.       DrawRotatingCam, state.rotatingAngles, state.penCenterX, state.penCenterY, $
  174.         state.rotatingCamCenterX, state.rotatingCamCenterY, state.color, $
  175.         state.rotatingRadius
  176.  
  177.         ; Draw the fixed cam first in the pixmap
  178.       DrawFixedCam, state.fixedAngles, state.fixedRadius, state.centerX, $
  179.         state.centerY
  180.  
  181.         ; Make the display window the current graph port
  182.       WSET, state.currentDrawArea
  183.  
  184.         ; Copy spirograph and cam from complete pixmap to the display window
  185.       DEVICE, COPY=[0, 0, 256, 256, 0, 0, state.completePixmap]
  186.  
  187.    ENDELSE
  188.  
  189. END
  190.  
  191.  
  192.     ;
  193.     ; Draw the complete spirograph showing cams and pen.  This procedure draws
  194.     ; only one "frame" of the spirograph animation.  It is called either
  195.     ; from a timer event. It will draw the cams, if needed, and one line segment
  196.     ; of the spirograph.
  197.     ;
  198.     ; The spirograph and cams are drawn using offscreen pixmaps.  This gives
  199.     ; the animation a much smoother appearance.  Without using pixmaps, the
  200.     ; erasing and drawing procedures would tend to have a flashing appearance.
  201.     ; The spirograph is drawn to its own pixmap called "spiroGraphPixmap". Each
  202.     ; call to this procedure adds another line segment to the pixmap. This
  203.     ; pixmap is then copied to another "work area" pixmap where the cams are
  204.     ; drawn in.  This one is called "completePixmap".  After the cams are added
  205.     ; this pixmap is then copied to the draw area of the window.
  206.     ;
  207. PRO DrawSpirographAndCam, state
  208.  
  209.      ; Increment angle of rotating cam within the fixed cam
  210.    state.camAngle = state.camAngle + state.camAngleIncr
  211.      ; Increment angle of the pen within the rotating cam
  212.    state.penAngle = state.penAngle + state.penAngleIncr
  213.  
  214.      ; If the cam rotates past the starting point - adjust it back
  215.    IF (state.camAngle GT state.maxAngle) THEN BEGIN
  216.       state.camAngle = state.maxAngle
  217.       state.penAngle = 0
  218.    ENDIF
  219.  
  220.      ; To draw this cam, we need to determine the location of its center
  221.      ; which is at some angle of rotation (camAngle) about the fixed cam.
  222.      ; Calculate the center of the rotating cam by adding its x and y
  223.      ; distance from the center of the draw area
  224.    state.rotatingCamCenterX = COS(state.camAngle) * state.rotatingCamDistance + state.centerX
  225.    state.rotatingCamCenterY = SIN(state.camAngle) * state.rotatingCamDistance + state.centerY
  226.  
  227.      ; Calculate the pen tip location relative to the rotating cam's center
  228.    penCenterX = COS(state.penAngle) * state.penRadius + state.rotatingCamCenterX
  229.    penCenterY = SIN(state.penAngle) * state.penRadius + state.rotatingCamCenterY
  230.  
  231.      ; We're drawing the spirograph in a separate off-screen pixmap
  232.      ; from the cams and then later combining them together.
  233.      ; So first set the pixmap to PLOTS the actual spirograph into
  234.    WSET, state.spiroGraphPixmap
  235.  
  236.      ; Draw spirograph line segment from the previous
  237.      ; pen location to the new pen location.
  238.    PLOTS,[state.penCenterX, penCenterX], [state.penCenterY, penCenterY], $
  239.          COLOR=GetColor(state.color), LINESTYLE=state.linestyle, /DEVICE
  240.  
  241.      ; Remember the new pen center
  242.    state.penCenterX = penCenterX
  243.    state.penCenterY = penCenterY
  244.  
  245.      ; Now draw the cams
  246.    DisplayCams, state
  247.  
  248. END
  249.  
  250.  
  251.     ;
  252.     ; Draw the complete spirograph without showing cams.
  253.     ; This is the procedure to draw the spirograph when the "Show Cams"
  254.     ; checkbox is not set, when the user starts the draw.  The whole
  255.     ; spirograph is drawn at one time, instead of the "frame at a time"
  256.     ; method used for animation by the "DrawSpirographAndCam" procedure.
  257.     ;
  258. PRO DrawSpirograph, state
  259.  
  260.      ; Display the wait cursor
  261.    WIDGET_CONTROL, /HOURGLASS
  262.  
  263.      ; Build an array that holds all the points of the spirograph
  264.      ; Add one to include the initial angle of zero radians
  265.    arraySize = CEIL(state.maxAngle/state.camAngleIncr) + 1
  266.  
  267.      ; Build and initialize arrays to the angles
  268.      ; of all positions for the rotating cam and associated pen
  269.      ; NOTE: Array Operation
  270.    camAngleArray = state.camAngleIncr * FINDGEN(arraySize)
  271.    penAngleArray = state.penAngleIncr * FINDGEN(arraySize)
  272.  
  273.      ; Make the display window the current output window
  274.    WSET, state.currentDrawArea
  275.  
  276.      ; Draw each line segment of the spirograph
  277.      ; NOTE: Array Operation
  278.    PLOTS, COS(penAngleArray) * state.penRadius + $
  279.      COS(camAngleArray) * state.rotatingCamDistance + state.centerX, $
  280.      SIN(penAngleArray) * state.penRadius + $
  281.      SIN(camAngleArray) * state.rotatingCamDistance + state.centerY, $
  282.      COLOR=GetColor(state.color), LINESTYLE=state.linestyle, /DEVICE
  283.  
  284.    WSET, state.spiroGraphPixmap  ; set the pixmap to copy spiro into
  285.      ; Copy spirograph from the display area to its pixmap
  286.    DEVICE, COPY=[0, 0, 256, 256, 0, 0, state.currentDrawArea]
  287.  
  288. END
  289.  
  290. ;================================================================================
  291. ;
  292. ;  The following five routines perform the required operation to create, update,
  293. ;  and display the spirograph compound widget.
  294. ;
  295. ;  SpiroEventHdlrCW     - Handles events associated with the spirograph compound
  296. ;                         widget. Only the timer event used for animating the cams
  297. ;                         is used by this routine.
  298. ;  EraseSpiroCW         - Erases the spirograph compound widget.
  299. ;  CreateSpiroCW        - Creates the compound widget. Creates pixmaps and other
  300. ;                         initial data. It sets up a state structure for keeping
  301. ;                         track of the state of the spirograph compound widget.
  302. ;  UpdateSpiroCW        - Whenever any spirograph parameters are changed, when the
  303. ;                         user manipulates one of the widgets, this
  304. ;                         routine is called to update the spirograph.
  305. ;  DrawSpiroCW          - Initiates the drawing sequence of the spirograph. This
  306. ;                         routine is called whenever the user clicks the Draw/Stop
  307. ;                         button is accomplished by either initiating a timer event,
  308. ;                         when animating the cam drawing of the spirograph or by just
  309. ;                         blasting the complete spirograph out to the display, when
  310. ;                         the cams are hidden and therefore no animation required.
  311. ;
  312. ;================================================================================
  313.  
  314.     ;
  315.     ; In this example, this event handler is only used for processing timer events.
  316.     ;
  317. FUNCTION SpiroEventHdlrCW, event
  318.  
  319.     swin = !D.WINDOW
  320.  
  321.      ; Retrieve the state structure from the first child of the compound widget.
  322.    spiroCW = event.handler
  323.    drawWidget = WIDGET_INFO(spiroCW, /CHILD)
  324.  
  325.      ; /NO_COPY kills the old uvalue
  326.    WIDGET_CONTROL, drawWidget, GET_UVALUE = state, /NO_COPY
  327.  
  328.      ; Default the event handler return value to zero.  This is a signal
  329.      ; to the event handling process that the passed in event was
  330.      ; processed and does not need to propagate up the event handling chain.
  331.    returnValue = 0
  332.  
  333.    CASE event.id OF
  334.       spiroCW: BEGIN   ; timer event
  335.  
  336.            ; Draw a frame of the spirograph and cams
  337.          DrawSpirographAndCam, state
  338.  
  339.            ; If not finished animating and a timer hasn't already been set
  340.            ; - set another timer to draw next frame
  341.          IF (state.camAngle LT state.maxAngle) AND (state.timerSet NE 0) THEN $
  342.  
  343.             WIDGET_CONTROL, spiroCW, TIMER=state.delay $
  344.  
  345.          ELSE BEGIN
  346.            ; Finished animating - reset angles and timer flag
  347.             IF (state.camAngle GE state.maxAngle) THEN BEGIN
  348.                state.camAngle = 0.0
  349.                state.penAngle = 0.0
  350.             ENDIF
  351.  
  352.             state.timerSet = 0 ; Reset timer flag to denote completion of animation
  353.  
  354.               ; Return an event to the compound widget's parent to signal
  355.               ; that the spirograph drawing has been completed
  356.             returnValue = $
  357.               {SpiroEventCW, ID:spiroCW, TOP:event.top, HANDLER:0L, VALUE:0L}
  358.  
  359.          ENDELSE
  360.       ENDCASE
  361.  
  362.       ELSE:
  363.  
  364.    ENDCASE
  365.  
  366.      ; Restore the state
  367.    WIDGET_CONTROL, drawWidget, SET_UVALUE = state, /NO_COPY
  368.  
  369.    WSET, swin
  370.    RETURN, returnValue
  371.  
  372. END
  373.  
  374.  
  375.     ;
  376.     ; Erase all the pixmaps and draw widget
  377.     ;
  378. PRO EraseSpiroCW, spiroCW
  379.  
  380.      ; Retrieve the structure from the child that contains the sub ids
  381.    drawWidget = WIDGET_INFO(spiroCW, /CHILD)
  382.      ; /NO_COPY kills the old uvalue
  383.    WIDGET_CONTROL, drawWidget, GET_UVALUE = state, /NO_COPY
  384.  
  385.    state.camAngle = 0.0
  386.    state.penAngle = 0.0
  387.  
  388.      ; Make the spirograph pixmap the current graph port
  389.    WSET, state.spiroGraphPixmap
  390.      ; Erase the spirograph from pixmap
  391.    ERASE
  392.  
  393.      ; Make the complete pixmap the current graph port
  394.    WSET, state.completePixmap
  395.      ; Erase the complete drawing (spirograph and cams) from pixmap
  396.    ERASE
  397.  
  398.      ; If the currentDrawArea has not been setup yet - save it value
  399.      ; This value is only available after the widget's been realized
  400.    IF (state.currentDrawArea EQ 0) THEN BEGIN
  401.  
  402.         ; Save the window's draw area window id for manipulating drawing
  403.         ; pixmaps
  404.       WIDGET_CONTROL, drawWidget, GET_VALUE=draw_win
  405.  
  406.       state.currentDrawArea = draw_win
  407.    ENDIF
  408.  
  409.      ; Make the display window the current graph port
  410.    WSET, state.currentDrawArea
  411.      ; Erase the drawing (spirograph and cams) from window
  412.    ERASE
  413.  
  414.      ; Restore the state variable
  415.    WIDGET_CONTROL, drawWidget, SET_UVALUE = state, /NO_COPY
  416.  
  417. END
  418.  
  419.  
  420.     ;
  421.     ; Clean up whenthe Spiro application is exited.
  422.     ; Remove the pixmap windows.
  423.     ;
  424. PRO CleanUpSpiroCW, drawWidgetID
  425.  
  426.    WIDGET_CONTROL, drawWidgetID, GET_UVALUE = state, /NO_COPY
  427.  
  428.      ; Remove the pixmaps - no longer needed
  429.    WDELETE, state.completePixmap
  430.    WDELETE, state.spiroGraphPixmap
  431.    
  432. END
  433.  
  434.  
  435.     ;
  436.     ; Create the spirograph compound widget
  437.     ;
  438. FUNCTION CreateSpiroCW, spiroWindow, UVALUE=uValue, $
  439.                         _EXTRA=otherKeywords
  440.  
  441.      ; The user can associate a user value with the this compound widget
  442.    IF NOT (KEYWORD_SET(uValue)) THEN $
  443.       uValue = 0
  444.  
  445.      ; Build arrays of angles (in radians) for drawing the rotating and
  446.      ; fixed cams (circles).
  447.      ; The rotating and fixed cams are drawn by plotting 60 line segments
  448.      ; at a distance of the cams radius from its center. This approximates
  449.      ; arcs of seven degrees. The shorter the line segments, the closer
  450.      ; the approximation to a circle.  The flip side to that is the shorter
  451.      ; the line segments, the more the line segments, the longer it takes
  452.      ; to draw.  Sixty line segments is a good compromise, fast yet still
  453.      ; looks like a circle.
  454.    rotatingAngles = findgen(60) * !DTOR * 7
  455.    fixedAngles = findgen(60) * !DTOR * 7
  456.  
  457.      ; The center of the rotating cam moves camAngleIncr radian increments
  458.      ; around the center of the fixed cam.
  459.    camAngleIncr = 0.05 * !PI
  460.  
  461.      ; Create base to encapsulate the compound widget
  462.    spiroCW = WIDGET_BASE(spiroWindow, UVALUE=uValue)
  463.  
  464.      ; Create the draw widget to draw the spirograph in
  465.    drawWidget = WIDGET_DRAW(spiroCW, /FRAME, KILL_NOTIFY="CleanUpSpiroCW", $
  466.                             _EXTRA=otherKeywords)
  467.  
  468.      ; Set the event handler function.
  469.      ; Make sure it lingers so the cleanup routine can get at its state.
  470.    WIDGET_CONTROL, spiroCW, SET_UVALUE = uValue, $
  471.      EVENT_FUNC = 'SpiroEventHdlrCW', /DELAY_DESTROY
  472.  
  473.      ; Create the pixmaps to draw into. These are only used when the cams are
  474.      ; shown.  The spirograph will be drawn in the spiroGraphPixmap (a line
  475.      ; segment per animation frame).  This is then bit copied to the
  476.      ; completePixmap where the rotating cam and pen are added to it. This
  477.      ; completePixmap will then be bit copied to the display window's draw
  478.      ; area. This is what gives us the smooth appearance of the cam animation.
  479.    WINDOW, /FREE, /PIXMAP, _EXTRA=otherKeywords
  480.    completePixmap = !D.WINDOW
  481.  
  482.    WINDOW, /FREE, /PIXMAP, _EXTRA=otherKeywords
  483.    spiroGraphPixmap = !D.WINDOW
  484.  
  485.    WIDGET_CONTROL, drawWidget, SET_UVALUE = $
  486.     { camAngleIncr: camAngleIncr, $ ; Rotating cam angle increment
  487.       penAngleIncr: 0.0, $          ; Pen angle increment
  488.       showCams : 0, $               ; Boolean show cams when true
  489.       delay: 0.0D, $                ; Delay between frames (in seconds)
  490.       timerSet:0, $                 ; Boolean flag - TRUE when timer exists
  491.       scale: 1.0, $                 ; scale to fit spirograph in draw area
  492.       centerX: 0.0, $               ; center of draw area
  493.       centerY: 0.0, $               ; center of draw area
  494.       fixedRadius: 0.0, $           ; radius of fixed cam
  495.       rotatingRadius: 0.0, $        ; radius of rotating cam
  496.       penRadius: 0.0, $             ; radius of pen in rotating cam
  497.       maxAngle: 0.0, $              ; number of radian loops for rotating cam
  498.       color: 0, $                   ; color index of pen
  499.       linestyle: 0, $               ; linestyle of pen
  500.       camAngle:0.0, $               ; angle of rotating cam
  501.       penAngle:0.0, $               ; angle of pen in rotating cam
  502.       rotatingCamCenterX:0.0, $     ; X location of center of rotating cam
  503.       rotatingCamCenterY:0.0, $     ; Y location of center of rotating cam
  504.       penCenterX:0.0, $             ; X location of center of pen tip
  505.       penCenterY:0.0, $             ; Y location of center of pen tip
  506.       currentDrawArea:0, $          ; current draw widget window id for spirograph
  507.       completePixmap:completePixmap, $  ; complete pixmap to draw spiro & cam
  508.       spiroGraphPixmap:spiroGraphPixmap, $  ; spirograph pixmap to draw spiro
  509.       rotatingCamDistance: 0.0, $   ; rotating cam center from fixed cam center
  510.       rotatingAngles: rotatingAngles, $ ; angles to draw rotating cam (circle)
  511.       fixedAngles: fixedAngles }     ; angles to draw fixed cam (circle)
  512.  
  513.    RETURN, spiroCW
  514. END
  515.  
  516.  
  517.     ;
  518.     ; Greatest Common Denominator
  519.     ;
  520. FUNCTION GreatComDenom, i, j
  521.    loc_i = i
  522.    loc_j = j
  523.  
  524.    WHILE (loc_j NE 0) DO BEGIN
  525.       temp = loc_j
  526.       loc_j = loc_i MOD loc_j
  527.       loc_i = temp;
  528.    ENDWHILE
  529.  
  530.    RETURN, loc_i
  531. END
  532.  
  533.  
  534.     ;
  535.     ; This function updates the spirograph parameters.  It is called
  536.     ; after the spirograph creation and any time the parameters change.
  537.     ;
  538. PRO UpdateSpiroCW, spiroCW, fixed_rad, rotating_rad, pen_rad, COLOR = color, $
  539.           LINESTYLE = linestyle, SHOWCAMS=showCams, CAMSPEED=camSpeed
  540.  
  541.    drawWidget = WIDGET_INFO(spiroCW, /CHILD)
  542.      ; /NO_COPY kills the old uvalue
  543.    WIDGET_CONTROL, drawWidget, GET_UVALUE = state, /NO_COPY
  544.  
  545.      ; Set the pen color
  546.    IF (N_ELEMENTS(color) eq 0) THEN $
  547.       state.color = !P.COLOR $
  548.    ELSE $
  549.       state.color = color
  550.  
  551.      ; Set the cam animation speed
  552.    IF (NOT KEYWORD_SET(camSpeed)) THEN $
  553.       state.delay = 0.0 $ ; default delay in fractions of a second
  554.    ELSE IF (camSpeed EQ 10) THEN $   ; If maximum speed -
  555.       state.delay = 0.0 $ ; Force shortest delay possible
  556.    ELSE $
  557.       state.delay = (10.0 - camSpeed) / 10.0  ; delay in fractions of a second
  558.  
  559.      ; Set the linestyle
  560.    IF (NOT KEYWORD_SET(linestyle)) THEN $
  561.       state.linestyle = 0 $
  562.    ELSE $
  563.       state.linestyle = linestyle
  564.  
  565.    state.fixedRadius = FLOAT(fixed_rad)
  566.    state.rotatingRadius = FLOAT(rotating_rad)
  567.    state.penRadius = FLOAT(pen_rad)
  568.  
  569.      ; Limit condition, cams can't be the same size
  570.    IF fixed_rad EQ rotating_rad THEN $
  571.       state.maxAngle = 0.0 $
  572.    ELSE IF pen_rad EQ 0 THEN $   ; A circle results with the pen at the center
  573.       state.maxAngle = 2.0 * !PI $
  574.    ELSE BEGIN                    ; Full number of radians to draw complete loop
  575.       radiiGCD = GreatComDenom(FIX(state.fixedRadius), FIX(state.rotatingRadius))
  576.       state.maxAngle = 2.0 * !PI * state.rotatingRadius / radiiGCD
  577.    ENDELSE
  578.  
  579.      ; Calculate the center of the draw widget
  580.    state.centerX = !D.X_SIZE / 2 -1
  581.    state.centerY = !D.Y_SIZE / 2 -1
  582.  
  583.      ; Scale the spirograph in order to fit completely within the draw widget
  584.    IF (!D.X_SIZE GT !D.Y_SIZE) THEN $
  585.       state.scale = !D.Y_SIZE $
  586.    ELSE $
  587.       state.scale = !D.X_SIZE
  588.  
  589.      ; Force worst case of pen distance to be 95% of the draw widget (radius)
  590.    state.scale = (state.scale * 0.95 / 2) / $
  591.      (ABS(state.fixedRadius - state.rotatingRadius) + state.penRadius)
  592.  
  593.      ; After determining the scale - apply it to the radii
  594.    state.fixedRadius = state.fixedRadius * state.scale
  595.    state.rotatingRadius = state.rotatingRadius * state.scale
  596.    state.penRadius = state.penRadius * state.scale
  597.  
  598.      ; Calculate the radian angle increment of the pen within the rotating cam
  599.    state.penAngleIncr = state.camAngleIncr - state.camAngleIncr * $
  600.          state.fixedRadius / state.rotatingRadius
  601.  
  602.      ; Calculate offset of center of rotating cam from center of fixed cam
  603.    state.rotatingCamDistance = state.fixedRadius - state.rotatingRadius
  604.  
  605.      ; To draw the rotating cam, we need to determine the location of its center
  606.      ; which is at some angle of rotation (camAngle) about the fixed cam.
  607.      ; Calculate the center of the rotating cam by adding its x and y
  608.      ; distance from the center of the draw area.
  609.    state.rotatingCamCenterX = $
  610.      COS(state.camAngle) * state.rotatingCamDistance + state.centerX
  611.    state.rotatingCamCenterY = $
  612.      SIN(state.camAngle) * state.rotatingCamDistance + state.centerY
  613.  
  614.      ; Calculate the pen tip location relative to the rotating cam's center
  615.    state.penCenterX = $
  616.      COS(state.penAngle) * state.penRadius + state.rotatingCamCenterX
  617.    state.penCenterY = $
  618.      SIN(state.penAngle) * state.penRadius + state.rotatingCamCenterY
  619.  
  620.      ; If the showCams keyword was not set - assume false
  621.    IF (NOT KEYWORD_SET(showCams)) THEN $
  622.       showCams = 0
  623.      ; Save the state of the showCams variable
  624.    state.showCams = showCams
  625.      ; Show the cams (if needed)
  626.    DisplayCams, state
  627.  
  628.      ; Restore the state
  629.    WIDGET_CONTROL, drawWidget, SET_UVALUE = state, /NO_COPY
  630.  
  631. END
  632.  
  633.  
  634.     ;
  635.     ; This function draws the spirograph and cams if needed
  636.     ; Called when the user hits the Draw/Stop button
  637.     ;
  638. PRO DrawSpiroCW, spiroCW, NOERASE = noerase, CAMSPEED=camSpeed, STOP=stop
  639.  
  640.    drawWidget = WIDGET_INFO(spiroCW, /CHILD)
  641.      ; /NO_COPY kills the old uvalue
  642.    WIDGET_CONTROL, drawWidget, GET_UVALUE = state, /NO_COPY
  643.  
  644.      ; If stopping the animation - user hit the stop button (i.e. Draw/Stop button)
  645.    IF KEYWORD_SET(stop) THEN $
  646.         ; Reset timer flag to indicate that the animation has stopped
  647.       state.timerSet = 0 $
  648.    ELSE BEGIN
  649.       WSET, state.spiroGraphPixmap
  650.  
  651.       IF NOT KEYWORD_SET(noerase) THEN ERASE
  652.  
  653.         ; If showCams is true
  654.       IF (state.showCams NE 0) THEN BEGIN
  655.  
  656.            ; Draw spirograph segments at timer intervals to in order
  657.            ; to animate the cam rotation.  A frame of the animation
  658.            ; will be drawn at each timer event. When the timer event occurs
  659.            ; in the spiroCW event handler, "SpiroEventHdlrCW", the drawing
  660.            ; routine "DrawSpirographAndCam" is called to draw a segment of
  661.            ; the spirograph and to display the next rotation of the cams. Also
  662.            ; the next timer event is established.
  663.  
  664.            ; If timer not already in progress - set timer
  665.          IF (state.timerSet EQ 0) THEN BEGIN
  666.               ; Set timer flag to keep track if its been set
  667.             state.timerSet = 1
  668.  
  669.               ; Establish a timer event to occur after a specific delay time
  670. print,spiroCW, state.delay
  671.             WIDGET_CONTROL, spiroCW, TIMER=state.delay
  672.  
  673.          ENDIF
  674.       ENDIF ELSE BEGIN
  675.            ; Not drawing cams -
  676.            ; There is no animation in this case. Just draw complete spirograph
  677.            ; directly to display.
  678.          DrawSpirograph, state
  679.  
  680.            ; Reset camangles back to zero
  681.          state.camAngle = 0.0
  682.          state.penAngle = 0.0
  683.  
  684.       ENDELSE
  685.    ENDELSE
  686.      ; Restore the state
  687.    WIDGET_CONTROL, drawWidget, SET_UVALUE = state, /NO_COPY
  688.  
  689. END
  690.  
  691. ;================================================================================
  692. ;
  693. ;  The following two routines create the Spiro widget application and handles events.
  694. ;
  695. ;  SpiroEventHdlr       - Process events for the Spiro widget application
  696. ;  Spiro                - Create the Spiro widget application. This includes
  697. ;                         creating a window and all of the controls including
  698. ;                         the spirograph compound widget that keeps track
  699. ;                         of drawing and animating the spirograph.
  700. ;
  701. ;================================================================================
  702.  
  703.     ;
  704.     ; Process events to the spirograph application
  705.     ;
  706. PRO SpiroEventHdlr, event
  707.   COMMON spiroCommon, cwSpiroWindow, cwPenColor, cFixedRadius, $
  708.             cRotatingRadius, cPenRadius, cPenColor, cLineStyle, cShowTheCams, $
  709.             cwDrawBase, cCamSpeed, cwCamSpeed, cwDrawButton
  710.  
  711.    WIDGET_CONTROL, event.id, GET_UVALUE=uValue
  712.  
  713.    swin = !D.WINDOW
  714.  
  715.    CASE (uValue) OF
  716.  
  717.            ; A "Line Style" radio button group event occurred.
  718.       0: cLineStyle = event.value
  719.  
  720.            ; A "Fixed Cam Radius" slider event occurred
  721.       3: cFixedRadius = event.value
  722.  
  723.            ; A "Rotating Cam Radius" slider event occurred
  724.       4: cRotatingRadius = event.value
  725.  
  726.            ; A "Pen Radius" (in rotating cam) slider event occurred
  727.       5: cPenRadius = event.value
  728.  
  729.            ; A "Pen Color" slider event occurred
  730.       6: cPenColor = event.value
  731.  
  732.            ; A "Show Cams" Checkbox event occurred
  733.       7: BEGIN
  734.          cShowTheCams = event.select
  735.            ; Only enable the CamSpeed slider if the cams
  736.            ; are visible. Otherwise the spirograph is drawn
  737.            ; in one draw action (PLOTS call)
  738.          WIDGET_CONTROL, cwCamSpeed, SENSITIVE=cShowTheCams
  739.          END
  740.  
  741.            ; A "Cam Speed" slider event occurred
  742.       8: cCamSpeed = event.value
  743.  
  744.            ; A "Draw/Stop" pushbutton event occurred
  745.       9: BEGIN
  746.  
  747.          WIDGET_CONTROL, event.id, GET_VALUE=buttonText
  748.  
  749.          IF buttonText EQ 'Draw' THEN BEGIN
  750.  
  751.               ; If currently not drawing - start drawing
  752.             DrawSpiroCW, cwDrawBase, /NOERASE, CAMSPEED=cCamSpeed
  753.  
  754.               ; If the cams are visible - the draw will be animated
  755.               ; In order to allow the user to stop the animation
  756.               ; change the "Draw" button to a "Stop" button
  757.             IF (cShowTheCams EQ 1) THEN $
  758.                WIDGET_CONTROL, event.id, SET_VALUE='Stop'
  759.  
  760.          ENDIF ELSE $
  761.               ; If the button label is not "Draw" it must be
  762.               ; "Stop". Therefore tell the Spirograph compound widget
  763.               ; to stop the animation.
  764.             DrawSpiroCW, cwDrawBase, /STOP
  765.  
  766.          END
  767.  
  768.             ; An "Erase" pushbutton event occurred -
  769.             ; Erase the spirograph.
  770.       10: EraseSpiroCW, cwDrawBase
  771.  
  772.             ; A CreateSpiroCW compound widget event occurred
  773.             ; This event occurs when the animated drawing has completed.
  774.             ; Therefore set the "Stop" button back to a "Draw" button.
  775.       11: WIDGET_CONTROL, cwDrawButton, SET_VALUE='Draw'
  776.  
  777.    ENDCASE
  778.  
  779.       ; Set new parameters and draw spirograph
  780.    UpdateSpiroCW, cwDrawBase, cFixedRadius, cRotatingRadius, cPenRadius, $
  781.      COLOR=cPenColor, LINESTYLE=cLineStyle, SHOWCAMS=cShowTheCams, $
  782.      CAMSPEED=cCamSpeed
  783.  
  784.    WSET, swin
  785. print, !d.window
  786. END
  787.  
  788.  
  789.     ;
  790.     ; Clean up after the spirograph application. Restore the previous
  791.     ; color table and the background color
  792.     ;
  793. PRO CleanUpSpiro, wSpiroWindow
  794.  
  795.      ; Get the color table saved in the window's user value
  796.    WIDGET_CONTROL, wSpiroWindow, GET_UVALUE=previousState
  797.    
  798.      ; Restore the previous color table.
  799.    TVLCT, previousState.colorTable
  800.  
  801.      ; Restore the previous background color.
  802.    !P.BACKGROUND = previousState.backgroundColor
  803.    
  804. END
  805.  
  806.  
  807.     ;
  808.     ; Create the spirograph window
  809.     ;
  810. PRO Spiro, GROUP = group
  811.  
  812.    COMMON spiroCommon
  813.  
  814.      ; If "Spiro" is already running (registered with the Xmanager)
  815.      ; exit back to where it was called from.
  816.    IF (XREGISTERED('SPIRO')) THEN $
  817.       RETURN      ; Only one copy at a time
  818.  
  819.    swin = !D.WINDOW        ; Save default window
  820.  
  821.     ; Get the current color vectors to restore when this application is exited.
  822.    TVLCT, savedR, savedG, savedB, /GET
  823.      ; Build color table from color vectors
  824.    colorTable = [[savedR],[savedG],[savedB]]
  825.    
  826.      ; Save the current background color in order to restore it when
  827.      ; the spiro application is exited
  828.    backgroundColor = !P.BACKGROUND
  829.    
  830.      ; Save items to be restored on exit in a structure
  831.    previousState = {colorTable:colorTable, backgroundColor:backgroundColor}
  832.    
  833.      ; Create the strings for the LineStyle radiobutton group.
  834.    lineStyleStrings = ['Solid', 'Dotted', 'Dashed', 'Dash Dot', $
  835.                        'Dash Dot Dot', 'Long Dashes' ]
  836.                        
  837.      ; Set up some initial values for the spirograph widgets
  838.    cFixedRadius = 24
  839.    cRotatingRadius = 10
  840.    cPenRadius = 41
  841.    cCamSpeed = 10   ; Initial draw speed of cams set to maximum speed
  842.    cShowTheCams = 1   ; Initially show the cams
  843.  
  844.      ; Load a particular color table so we can have a good idea of
  845.      ; the colors we're working with
  846.    loadct, 39, /SILENT
  847.  
  848.      ; Initialize the background color to white
  849.    !P.BACKGROUND = GetColor(!D.TABLE_SIZE - 1)
  850.  
  851.      ; Initialize the pen color table index to red
  852.    cPenColor = !D.TABLE_SIZE - 2
  853.    
  854.      ; Save the default plotting line style
  855.    cLineStyle = !P.LINESTYLE
  856.  
  857.      ; Create the main window - non-sizable
  858.    cwSpiroWindow = WIDGET_BASE(TITLE='Spirographics', XOFFSET=30, $
  859.                     YOFFSET=30, /ROW, TLB_FRAME_ATTR=1)
  860.  
  861.     ; Setting the managed attribute indicates our intention to put this app
  862.     ; under the control of XMANAGER, and prevents our draw widgets from
  863.     ; becoming candidates for becoming the default window on WSET, -1. XMANAGER
  864.     ; sets this, but doing it here prevents our own WSETs at startup from
  865.     ; having that problem.
  866.    WIDGET_CONTROL, /MANAGED, cwSpiroWindow
  867.  
  868.      ; Create the base to hold the spirograph controls
  869.    wControlBase = WIDGET_BASE(cwSpiroWindow, /ROW)
  870.  
  871.      ; Create the compound spiro widget - draw widget and parameters
  872.    cwDrawBase = CreateSpiroCW(cwSpiroWindow, UVALUE=11, $
  873.                               XSIZE=256, YSIZE=256)
  874.  
  875.      ; Create base for the spirograph parameters - all sliders
  876.    wSliderBase = WIDGET_BASE(wControlBase, /COLUMN)
  877.  
  878.      ; Create slider control to set the fixed cam radius
  879.    wFixedRadius = WIDGET_SLIDER(wSliderBase, TITLE='Fixed Cam Radius', $
  880.                                 VALUE=cFixedRadius, /DRAG, UVALUE=3)
  881.  
  882.      ; Create slider control to set the rotating cam radius
  883.    wRotatingRadius = WIDGET_SLIDER(wSliderBase, TITLE='Rotating Cam Radius', $
  884.                    VALUE = cRotatingRadius, MAX = 100, MIN=1, /DRAG, UVALUE=4)
  885.  
  886.      ; Create slider control to set the pen radius in the rotating cam
  887.    wPenRadius = WIDGET_SLIDER(wSliderBase, TITLE='Pen Radius', $
  888.                               VALUE=cPenRadius, /DRAG, UVALUE=5)
  889.  
  890.      ; Create slider control to set the pen color
  891.    cwPenColor = WIDGET_SLIDER(wSliderBase, TITLE='Pen Color', $
  892.                               /DRAG, MAX = !D.TABLE_SIZE-2, UVALUE=6)
  893.    
  894.      ; Set the "Pen Color" slider value to reflect the current pen color table index
  895.    WIDGET_CONTROL, cwPenColor, SET_VALUE = cPenColor
  896.  
  897.      ; Create base for linestyle, showCams checkbox, and cam speed control
  898.    wButtonBase = WIDGET_BASE(wControlBase, /COLUMN)
  899.  
  900.      ; Create linestyle exclusive base
  901.    wLinestyleBase = CW_BGROUP(wButtonBase, lineStyleStrings, /COLUMN, $
  902.                               /EXCLUSIVE, /FRAME, SET_VALUE=0, UVALUE=0, $
  903.                               /NO_RELEASE, LABEL_TOP = "Line Style:")
  904.  
  905.      ; Create "showCams" checkbox nonexclusive base
  906.    wChkBoxBase = WIDGET_BASE(wButtonBase, /COLUMN, /NONEXCLUSIVE)
  907.  
  908.      ; Create "showCams" checkbox
  909.    wShowCams = WIDGET_BUTTON(wChkBoxBase, VALUE='Show Cams', UVALUE=7)
  910.      ; Set up the initial value of the checkbox
  911.    WIDGET_CONTROL, wShowCams, SET_BUTTON = cShowTheCams
  912.  
  913.      ; Create cam speed slider
  914.    cwCamSpeed = WIDGET_SLIDER(wButtonBase, TITLE='Cam Speed', VALUE=cCamSpeed, $
  915.                               MAX = 10, MIN=1, /DRAG, UVALUE=8)
  916.  
  917.      ; Add base and pushbuttons to Draw and Erase
  918.    wPushButtonBase = WIDGET_BASE(wSliderBase, /ROW, YPAD=20)
  919.  
  920.      ; Create "Draw/Stop" button.
  921.      ; This button we read "Draw" when the spirograph is not drawing,
  922.      ; and "Stop" during drawing operations
  923.    cwDrawButton = WIDGET_BUTTON(wPushButtonBase, VALUE='Draw', UVALUE=9)
  924.  
  925.      ; Create "Erase" button
  926.    wEraseButton = WIDGET_BUTTON(wPushButtonBase, VALUE='Erase', UVALUE=10)
  927.  
  928.      ; Save the previous color table in the user value to retore on exit
  929.    WIDGET_CONTROL, cwSpiroWindow, SET_UVALUE=previousState
  930.    
  931.      ; Make the window visible
  932.    WIDGET_CONTROL, /REALIZE, cwSpiroWindow  ; Show the window
  933.  
  934.      ; Setup the device to use the pointer cursors
  935.    DEVICE, /CURSOR_ORIGINAL
  936.  
  937.      ; Erase the Spirograph to update the draw areas background color
  938.    EraseSpiroCW, cwDrawBase
  939.  
  940.      ; Set the spirograph parameters and draw the cams
  941.    UpdateSpiroCW, cwDrawBase, cFixedRadius, cRotatingRadius, cPenRadius, $
  942.              COLOR = cPenColor, LINESTYLE = cLineStyle, SHOWCAMS=cShowTheCams,$
  943.              CAMSPEED=cCamSpeed
  944.  
  945.    WSET, swin                ; Restore default window
  946.  
  947.    XMANAGER, 'SPIRO', cwSpiroWindow, GROUP_LEADER = GROUP, $
  948.      EVENT_HANDLER="SpiroEventHdlr", CLEANUP="CleanUpSpiro", /NO_BLOCK
  949. END
  950.  
  951.